//---------------------------------------------------------------------------------
// Copyright (c) 2011, Microsoft Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//---------------------------------------------------------------------------------

namespace Microsoft.Samples.ServiceBus.SilverlightChat.Models.ViewModels
{
    using System;
    using System.Net;
    using System.Windows;
    using Microsoft.Samples.ServiceBus;
    using Microsoft.Samples.ServiceBus.Messaging;
    using Microsoft.Samples.ServiceBus.SilverlightChat.Models.Commands;

    /// <summary>
    /// Encapsulates the view model for a chat topic (each item displayed on the 
    /// left of the main chat window). If the topic is currently selected (indicated
    /// by the IsCurrentTopic property) and a subscription exists (indicated by the
    /// IsSubscribed property) then a message receiver will continually attempt to
    /// receive messages sent to the subscription and display them in the UI
    /// </summary>
    public class ChatTopicViewModel : DependencyObject
    {
        /// <summary>
        /// Dependency Property used to determine if a subscription exists on this
        /// topic - a Dependency Property is used to allow Binding to this property,
        /// for example to the 'star' button which highlights/dims given this value.
        /// </summary>
        public static readonly DependencyProperty IsSubscribedProperty =
            DependencyProperty.Register("IsSubscribed", typeof(bool), typeof(ChatTopicViewModel), new PropertyMetadata(false));

        // These commands accept the topic they will act on as the CommandParameter,
        // allowing a single static instance to serve all command executions
        private static readonly SubscribeToTopicCommand subscribeCommand = new SubscribeToTopicCommand();
        private static readonly DeleteTopicCommand deleteTopicCommand = new DeleteTopicCommand();

        private readonly TopicClient topicClient;
        private readonly ChatTopicListViewModel owner;

        private SubscriptionClient subscriptionClient;
        private bool isCurrentTopic;

        public ChatTopicViewModel(TopicClient topicClient, ChatTopicListViewModel owner)
        {
            if (topicClient == null)
            {
                throw new ArgumentNullException("topicClient");
            }

            this.topicClient = topicClient;
            this.owner = owner;
        }

        public bool IsSubscribed
        {
            get { return (bool)GetValue(IsSubscribedProperty); }
            set { SetValue(IsSubscribedProperty, value); }
        }

        public bool IsCurrentTopic
        {
            get
            {
                return this.isCurrentTopic;
            }

            set
            {
                if (this.isCurrentTopic != value)
                {
                    this.isCurrentTopic = value;

                    // When the topic is selected/deselected or it's subscription 
                    // status changes, this method is called to start/end receiving
                    // messages.
                    this.IsCurrentTopicChanged();
                }
            }
        }

        public string Name
        {
            get { return this.topicClient.Path; }
        }

        public TopicClient TopicClient
        {
            get
            {
                return this.topicClient;
            }
        }

        public SubscriptionClient SubscriptionClient
        {
            get
            {
                return this.subscriptionClient;
            }

            set
            {
                if (this.subscriptionClient != value)
                {
                    if (value != null)
                    {
                        // Use Peek Lock receive mode with this client
                        value.ReceiveMode = ReceiveMode.PeekLock;
                    }

                    this.subscriptionClient = value;
                    this.IsSubscribed = value != null;

                    // If this is the selected topic, begin/end receiving from this subscription
                    if (this.isCurrentTopic)
                    {
                        this.IsCurrentTopicChanged();
                    }
                }
            }
        }

        /// <summary>
        /// Gets the command used to subscribe to or unsubscribe from a topic
        /// </summary>
        public SubscribeToTopicCommand SubscribeCommand
        {
            get { return subscribeCommand; }
        }

        /// <summary>
        /// Gets the command used to delete a topic
        /// </summary>
        public DeleteTopicCommand DeleteTopicCommand
        {
            get { return deleteTopicCommand; }
        }

        /// <summary>
        /// When the current topic changes (that is, when its subscription status 
        /// chages, or it becomes either selected or deselected) a receiver is started
        /// or ended approrpiately.
        /// </summary>
        private void IsCurrentTopicChanged()
        {
            // If this topic is selected, and it has a subscription, being receiving
            if (this.isCurrentTopic && this.subscriptionClient != null)
            {
                this.subscriptionClient.BeginReceive(this.OnReceiveMessageCompleted, this.subscriptionClient);
            }
            else
            {
                // in any other case, set the receiver to null.
                this.subscriptionClient = null;
            }

            // Update the UI: if the currently selected topic does not have a 
            // subscription display a message indicating that the user should 
            // subscribe; otherwise, display chat messages.
            if (this.isCurrentTopic)
            {
                var state = (this.subscriptionClient != null) ? "Subscribed" : "Unsubscribed";

                var mainPage = (MainPage)App.Current.RootVisual;
                VisualStateManager.GoToState(mainPage.chatWindow, state, false);
            }
        }

        private void OnReceiveMessageCompleted(IAsyncResult result)
        {
            var subscriptionClient = (SubscriptionClient)result.AsyncState;
            try
            {
                var message = subscriptionClient.EndReceive(result);

                // if a message is received and this is the current topic, process 
                // the message. Otherwise, Abandon the message to return it to the 
                // subscription.
                if (this.isCurrentTopic)
                {
                    // if a message was received, display it in the UI and then
                    // complete it.
                    if (message != null)
                    {
                        this.Dispatcher.BeginInvoke(() =>
                            {
                                var mainPage = (MainPage)App.Current.RootVisual;
                                mainPage.chatWindow.AppendMessage(this, message);
                                subscriptionClient.BeginComplete(message, OnCompleteMessageCompleted, subscriptionClient);
                            });
                    }

                    // if we're still the current topic and have an active receiver
                    // try to receive the next message from the subscription
                    if (this.subscriptionClient != null && this.isCurrentTopic)
                    {
                        this.subscriptionClient.BeginReceive(this.OnReceiveMessageCompleted, this.subscriptionClient);
                    }
                }
                else
                {
                    // if the receive completed after this topic ceased to be the 
                    // current topic (i.e. IsCurrentTopic = false) abandon any
                    // received message
                    if (message != null)
                    {
                        subscriptionClient.BeginAbandon(message, this.OnAbandonMessageCompleted, null);
                    }
                }
            }
            catch (HttpWebException w)
            {
                // If the topic (or subscription) is deleted which a receive is in
                // progress, then a 404 (NotFound) exception is thrown at EndReceive
                if (w.StatusCode == HttpStatusCode.NotFound)
                {
                    // The topic does not exist, it may have been deleted
                    // so remove it from the UI
                    if (this.IsCurrentTopic && this.owner != null)
                    {
                        this.owner.RemoveTopics(this.topicClient);
                    }
                }
            }
            catch (Exception e)
            {
                App.ShowError(e);
            }
        }

        private void OnCompleteMessageCompleted(IAsyncResult result)
        {
            var subscriptionClient = (SubscriptionClient)result.AsyncState;

            try
            {
                subscriptionClient.EndComplete(result);
            }
            catch (Exception e)
            {
                App.ShowError(e);
            }
        }

        private void OnAbandonMessageCompleted(IAsyncResult result)
        {
            var subscriptionClient = (SubscriptionClient)result.AsyncState;

            try
            {
                subscriptionClient.EndAbandon(result);
            }
            catch (Exception e)
            {
                App.ShowError(e);
            }
        }
    }
}
